package com.cnwidsom.plugins.type;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import com.cnwidsom.utils.ClassUtils;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

public class DubboTypeInjector implements TypeInjector {
	private static boolean isPoolInjected = false;
	private Map<String, String> params;
	private final Pattern[] CAN_INJECT_NAMES = new Pattern[] {
			Pattern.compile("com\\.alibaba\\.dubbo\\.remoting\\.exchange\\.support\\.header\\.HeaderExchangeChannel"),
			Pattern.compile("com\\.alibaba\\.dubbo\\.common\\.bytecode\\.Wrapper(\\d+)"),
			Pattern.compile("org\\.jboss\\.resteasy\\.client\\.jaxrs\\.engines\\.ApacheHttpClient4Engine"),
			Pattern.compile("com\\.alibaba\\.dubbo\\.rpc\\.protocol\\.rest\\.DubboHttpServer\\$RestHandler"),
			Pattern.compile("com\\.alibaba\\.dubbo\\.common\\.bytecode\\.Wrapper"),
			Pattern.compile("com\\.alibaba\\.dubbo\\.remoting\\.exchange\\.support\\.header\\.HeaderExchangeHandler") };

	public String getName() {
		return "dubbo";
	}

	private void injectClassGenerator(ClassLoader loader, ClassPool cp) throws Exception {
		if (!isPoolInjected) {
			try {
				Class classGenerator = Class.forName("com.alibaba.dubbo.common.bytecode.ClassGenerator");
				Field pool_map_field = classGenerator.getDeclaredField("POOL_MAP");
				// ConcurrentHashMap m;
				pool_map_field.setAccessible(true);
				Object pool_map = pool_map_field.get(null);
				Method pool_map_put = pool_map.getClass().getMethod("put", Object.class, Object.class);
				Method pool_map_get = pool_map.getClass().getMethod("get", Object.class);
				if (pool_map_get.invoke(pool_map, loader) == null) {
					pool_map_put.invoke(pool_map, loader, cp);
				}
				isPoolInjected = true;
			} catch (ClassNotFoundException ex) {
			} catch (NoSuchFieldException ex) {
			} catch (SecurityException ex) {
			} catch (IllegalArgumentException ex) {
			} catch (IllegalAccessException ex) {
			} catch (NoSuchMethodException ex) {
			} catch (InvocationTargetException ex) {
			} catch (Exception ex) {
				throw ex;
			}
		}
	}

	private void injectDubboProtocol(CtClass ctClass, CtMethod method) throws CannotCompileException, IOException, NotFoundException {
		System.out.println("Injecting " + getName() + "[" + method.getLongName() + "]...");
		String old_name = method.getName();
		String new_name = old_name + "$impl";

		CtMethod method_new = CtNewMethod.copy(method, new_name, ctClass, null);
		// 增加新方法
		ctClass.addMethod(method_new);

		CtClass returnClass = method.getReturnType();
		if (returnClass.getName().equalsIgnoreCase("void")) {
			returnClass = null;
		}

		StringBuffer codeAppender = new StringBuffer();
		codeAppender.append("{\n");

		// 修改Method中ParamterTypes数量,后续调用需要此参数
		codeAppender.append("if($1 instanceof com.alibaba.dubbo.rpc.RpcInvocation){\n");
		codeAppender.append("System.out.println(\"Calling " + method.getLongName() + "\");\n");
		codeAppender.append("com.alibaba.dubbo.rpc.RpcInvocation invocation=(com.alibaba.dubbo.rpc.RpcInvocation)$1;\n");

		codeAppender.append("java.lang.reflect.Field field = $1.getClass().getDeclaredField(\"parameterTypes\");\n");
		codeAppender.append("field.setAccessible(true);\n");
		codeAppender.append("java.lang.Class[] paramClasses = (java.lang.Class[]) field.get($1);\n");

		codeAppender.append("Object[] args = invocation.getArguments();\n");
		codeAppender.append("boolean isInjected = false;\n");

		codeAppender.append("for(int i=0;i<paramClasses.length;i++){\n");
		codeAppender.append("java.lang.Class clazz=paramClasses[i];\n");
		codeAppender.append("if(clazz.getName().equals(\"com.cnwidsom.monitor.Token\")){\n");
		codeAppender.append("isInjected=true;\n");
		codeAppender.append("break;\n");
		codeAppender.append("}\n");
		codeAppender.append("}\n");

		codeAppender.append("if(!isInjected){\n");
		codeAppender.append("java.lang.Class[] new_paramClasses = java.util.Arrays.copyOf(paramClasses, paramClasses.length + 1);\n");
		codeAppender.append("new_paramClasses[new_paramClasses.length - 1] = com.cnwidsom.monitor.Token.class;\n");
		codeAppender.append("field.set($1, new_paramClasses);\n");
		codeAppender.append("}\n");

		codeAppender.append("com.cnwidsom.monitor.Token.createNextToken(\"" + old_name + "-start\",com.cnwidsom.monitor.Token.TYPE_DISTRIBUTE_CALL,true);\n");
		codeAppender.append("com.cnwidsom.monitor.Token header = com.cnwidsom.monitor.Token.getTokenHeader();\n");
		codeAppender.append("com.cnwidsom.monitor.Token.createNextToken(\"" + old_name + "\",com.cnwidsom.monitor.Token.TYPE_DISTRIBUTE_CALL,false);\n");

		codeAppender.append("if(!isInjected){\n");
		codeAppender.append("Object[] newObj = java.util.Arrays.copyOf(args, args.length + 1);\n");
		codeAppender.append("newObj[newObj.length - 1] = com.cnwidsom.monitor.Token.getTokenHeader();\n");
		codeAppender.append("invocation.setArguments(newObj);\n");
		codeAppender.append("}\n");

		codeAppender.append("java.util.List ex_list = new java.util.ArrayList();\n");
		codeAppender.append("long start = System.currentTimeMillis();\n");
		codeAppender.append("try{\n");
		if (returnClass == null)
			codeAppender.append(new_name + "($$);");
		else
			codeAppender.append("return " + new_name + "($$);");

		codeAppender.append("}catch(com.alibaba.dubbo.remoting.RemotingException ex1){\n");
		codeAppender.append("ex_list.add(ex1);\n");
		codeAppender.append("throw ex1;\n");
		codeAppender.append("}finally{\n");

		codeAppender.append("long runTime = System.currentTimeMillis()-start;\n");

		codeAppender.append("java.util.Map extend_object = new java.util.HashMap();\n");

		codeAppender.append("com.cnwidsom.monitor.MonitorItem mi = new com.cnwidsom.monitor.MonitorItem(header, \"" + method.getLongName()
				+ "\", runTime, ex_list, System.currentTimeMillis(), extend_object);\n");
		codeAppender.append("com.cnwidsom.monitor.MonitorItem mi2 = new com.cnwidsom.monitor.MonitorItem(com.cnwidsom.monitor.Token.getTokenHeader(), \""
				+ method.getLongName() + "\", runTime, ex_list, System.currentTimeMillis(), extend_object);\n");
		codeAppender.append("java.util.Map params = new java.util.HashMap();\n");

		// 记录调用参数

		codeAppender.append("mi.setParameters(params);\n");
		codeAppender.append("com.cnwidsom.monitor.Token.endBackToken(mi2);\n");
		codeAppender.append("com.cnwidsom.monitor.Token.endBackToken(mi);\n");
		codeAppender.append("java.util.List token_list = com.cnwidsom.monitor.Token.releaseToken();\n");
		codeAppender.append("if(token_list!=null&&token_list.size()>0){\n");
		codeAppender.append("com.cnwidsom.monitor.MonitorLogWritter.log(token_list);\n");
		codeAppender.append("}\n");

		codeAppender.append("}\n");
		codeAppender.append("}else{\n");
		if (returnClass == null)
			codeAppender.append(new_name + "($$);\n");
		else
			codeAppender.append("return " + new_name + "($$);\n");
		codeAppender.append("}\n");
		codeAppender.append("}");

		method.setBody(codeAppender.toString());
	}

	public byte[] inject(ClassLoader loader, CtClass ctClass) {
		try {
			String type = params.get("type");

			if (CAN_INJECT_NAMES[0].matcher(ctClass.getName()).matches() && type.contains("dubbo")) {
				CtMethod method_send = ClassUtils.getMatchMethod(ctClass, "send", new String[] { "java.lang.Object", "boolean" });
				CtMethod method_request = ClassUtils.getMatchMethod(ctClass, "request", new String[] { "java.lang.Object", "int" });
				injectDubboProtocol(ctClass, method_send);
				injectDubboProtocol(ctClass, method_request);

				return ctClass.toBytecode();
			} else if (CAN_INJECT_NAMES[1].matcher(ctClass.getName()).matches() && type.contains("dubbo")) {
				CtMethod method = ClassUtils.getMatchMethod(ctClass, "invokeMethod",
						new String[] { "java.lang.Object", "java.lang.String", "java.lang.Class[]", "java.lang.Object[]" });

				System.out.println("Injecting " + getName() + "[" + method.getLongName() + "]...");
				String old_name = method.getName();
				String new_name = old_name + "$impl";

				CtMethod method_new = CtNewMethod.copy(method, new_name, ctClass, null);
				// 增加新方法
				ctClass.addMethod(method_new);

				StringBuffer codeAppender = new StringBuffer();
				codeAppender.append("{\n");

				codeAppender.append("Class[] origin_params;\n");
				codeAppender.append("Object[] origin_args;\n");
				codeAppender.append("com.cnwidsom.monitor.Token dToken = null;\n");

				codeAppender.append("System.out.println(\"Calling " + method.getLongName() + "\");\n");

				codeAppender.append("boolean isMonitor = ($3 != null && $3.length > 0) ? true : false;\n");

				codeAppender.append("if(isMonitor && $3[$3.length - 1].getName().equals(\"com.cnwidsom.monitor.Token\")) {\n");
				codeAppender.append("dToken = (com.cnwidsom.monitor.Token)$4[$4.length - 1];\n");
				codeAppender.append("dToken.setNotRecord(false);\n");
				codeAppender.append("origin_params = (java.lang.Class[])java.util.Arrays.copyOf($3, $3.length - 1);\n");
				codeAppender.append("origin_args = java.util.Arrays.copyOf($4, $4.length - 1);\n");
				codeAppender.append("com.cnwidsom.monitor.Token.distributeToken(dToken);\n");

				codeAppender.append("} else {\n");
				codeAppender.append("origin_params = $3;\n");
				codeAppender.append("origin_args = $4;\n");
				codeAppender.append("dToken = com.cnwidsom.monitor.Token.createNopToken();\n");
				codeAppender.append("}\n");

				codeAppender.append("java.util.List ex_list = new java.util.ArrayList();\n");
				codeAppender.append("long start = System.currentTimeMillis();\n");
				codeAppender.append("try{\n");
				codeAppender.append("return " + new_name + "($1,$2,origin_params,origin_args);");
				codeAppender.append("}catch(Throwable ex1){\n");
				codeAppender.append("ex_list.add(ex1);\n");
				codeAppender.append("throw ex1;\n");
				codeAppender.append("}finally{\n");

				codeAppender.append("if(isMonitor){\n");

				codeAppender.append("long runTime = System.currentTimeMillis()-start;\n");

				codeAppender.append("java.util.Map extend_object = new java.util.HashMap();\n");

				codeAppender.append("com.cnwidsom.monitor.MonitorItem mi = new com.cnwidsom.monitor.MonitorItem(com.cnwidsom.monitor.Token.getTokenHeader(), \""
						+ method.getLongName() + "\", runTime, ex_list, System.currentTimeMillis(), extend_object);\n");
				codeAppender.append("java.util.Map params = new java.util.HashMap();\n");

				// 记录调用参数

				codeAppender.append("mi.setParameters(params);\n");
				codeAppender.append("com.cnwidsom.monitor.Token endToken=com.cnwidsom.monitor.Token.endBackToken(mi, true);\n");
				codeAppender.append("java.util.List token_list = com.cnwidsom.monitor.Token.releaseToken();\n");
				codeAppender.append("System.out.println(\"pre_log\");\n");

				codeAppender.append("if(token_list!=null&&token_list.size()>0){\n");
				codeAppender.append("System.out.println(\"log\");\n");
				codeAppender.append("com.cnwidsom.monitor.MonitorLogWritter.log(token_list);\n");
				codeAppender.append("}\n");

				codeAppender.append("}\n");// end if(isNoMonitor)

				codeAppender.append("}\n");
				codeAppender.append("}");

				method.setBody(codeAppender.toString());

				return ctClass.toBytecode();
			} else if (CAN_INJECT_NAMES[2].matcher(ctClass.getName()).matches() && type.contains("rest")) {
				CtMethod method = ClassUtils.getMatchMethod(ctClass, "loadHttpMethod",
						new String[] { "org.jboss.resteasy.client.jaxrs.internal.ClientInvocation", "org.apache.http.client.methods.HttpRequestBase" });

				System.out.println("Injecting " + getName() + "[" + method.getLongName() + "]...");
				String old_name = method.getName();
				String new_name = old_name + "$impl";

				CtMethod method_new = CtNewMethod.copy(method, new_name, ctClass, null);
				// 增加新方法
				ctClass.addMethod(method_new);

				StringBuffer codeAppender = new StringBuffer();
				codeAppender.append("{\n");

				// 修改Method中ParamterTypes数量,后续调用需要此参数
				codeAppender.append("System.out.println(\"Calling " + method.getLongName() + "\");\n");

				codeAppender.append(
						"com.cnwidsom.monitor.Token.createNextToken(\"" + old_name + "-start\",com.cnwidsom.monitor.Token.TYPE_DISTRIBUTE_CALL,true);\n");
				codeAppender.append("com.cnwidsom.monitor.Token header = com.cnwidsom.monitor.Token.getTokenHeader();\n");
				codeAppender
						.append("com.cnwidsom.monitor.Token.createNextToken(\"" + old_name + "\",com.cnwidsom.monitor.Token.TYPE_DISTRIBUTE_CALL,false);\n");
				codeAppender
						.append("$2.addHeader(\"X-INVOKE-PATH-TOKEN\", com.cnwidsom.monitor.Token.getTokenHeader().getCurTokenValue() + \"|\" + com.cnwidsom.monitor.Token.getTokenHeader().getPreviousToken().getCurTokenValue()+\"|"
								+ old_name + "\");");
				codeAppender.append("java.util.List ex_list = new java.util.ArrayList();\n");
				codeAppender.append("long start = System.currentTimeMillis();\n");
				codeAppender.append("try{\n");
				codeAppender.append("return " + new_name + "($$);");
				codeAppender.append("}catch(Throwable ex1){\n");
				codeAppender.append("ex_list.add(ex1);\n");
				codeAppender.append("throw ex1;\n");
				codeAppender.append("}finally{\n");

				codeAppender.append("long runTime = System.currentTimeMillis()-start;\n");

				codeAppender.append("java.util.Map extend_object = new java.util.HashMap();\n");

				codeAppender.append("com.cnwidsom.monitor.MonitorItem mi = new com.cnwidsom.monitor.MonitorItem(header, \"" + method.getLongName()
						+ "\", runTime, ex_list, System.currentTimeMillis(), extend_object);\n");
				codeAppender
						.append("com.cnwidsom.monitor.MonitorItem mi2 = new com.cnwidsom.monitor.MonitorItem(com.cnwidsom.monitor.Token.getTokenHeader(), \""
								+ method.getLongName() + "\", runTime, ex_list, System.currentTimeMillis(), extend_object);\n");
				codeAppender.append("java.util.Map params = new java.util.HashMap();\n");

				// 记录调用参数

				codeAppender.append("mi.setParameters(params);\n");
				codeAppender.append("com.cnwidsom.monitor.Token.endBackToken(mi2);\n");
				codeAppender.append("com.cnwidsom.monitor.Token.endBackToken(mi);\n");
				codeAppender.append("java.util.List token_list = com.cnwidsom.monitor.Token.releaseToken();\n");
				codeAppender.append("if(token_list!=null&&token_list.size()>0){\n");
				codeAppender.append("com.cnwidsom.monitor.MonitorLogWritter.log(token_list);\n");
				codeAppender.append("}\n");

				codeAppender.append("}\n");
				codeAppender.append("}");

				method.setBody(codeAppender.toString());

				return ctClass.toBytecode();
			} else if (CAN_INJECT_NAMES[3].matcher(ctClass.getName()).matches() && type.contains("rest")) {
				CtMethod method = ClassUtils.getMatchMethod(ctClass, "handle",
						new String[] { "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse" });

				System.out.println("Injecting " + getName() + "[" + method.getLongName() + "]...");
				String old_name = method.getName();
				String new_name = old_name + "$impl";

				CtMethod method_new = CtNewMethod.copy(method, new_name, ctClass, null);
				// 增加新方法
				ctClass.addMethod(method_new);

				StringBuffer codeAppender = new StringBuffer();
				codeAppender.append("{\n");
				codeAppender.append("System.out.println(\"Calling " + method.getLongName() + "\");\n");

				codeAppender.append("String headerStr = $1.getHeader(\"X-INVOKE-PATH-TOKEN\");\n");
				codeAppender.append("String[] headers = headerStr.split(\"\\\\|\");\n");
				codeAppender.append(
						"com.cnwidsom.monitor.Token dToken = com.cnwidsom.monitor.Token.distributeToken(\"" + old_name + "\", headers[0], headers[1]);\n");

				codeAppender.append("java.util.List ex_list = new java.util.ArrayList();\n");
				codeAppender.append("long start = System.currentTimeMillis();\n");
				codeAppender.append("try{\n");
				codeAppender.append("return " + new_name + "($$);");
				codeAppender.append("}catch(Throwable ex1){\n");
				codeAppender.append("ex_list.add(ex1);\n");
				codeAppender.append("throw ex1;\n");
				codeAppender.append("}finally{\n");

				codeAppender.append("long runTime = System.currentTimeMillis()-start;\n");

				codeAppender.append("java.util.Map extend_object = new java.util.HashMap();\n");

				codeAppender.append("com.cnwidsom.monitor.MonitorItem mi = new com.cnwidsom.monitor.MonitorItem(com.cnwidsom.monitor.Token.getTokenHeader(), \""
						+ method.getLongName() + "\", runTime, ex_list, System.currentTimeMillis(), extend_object);\n");
				codeAppender.append("java.util.Map params = new java.util.HashMap();\n");

				// 记录调用参数

				codeAppender.append("mi.setParameters(params);\n");
				codeAppender.append("com.cnwidsom.monitor.Token.endBackToken(mi, true);\n");
				codeAppender.append("java.util.List token_list = com.cnwidsom.monitor.Token.releaseToken();\n");
				codeAppender.append("if(token_list!=null&&token_list.size()>0){\n");
				codeAppender.append("com.cnwidsom.monitor.MonitorLogWritter.log(token_list);\n");
				codeAppender.append("}\n");

				codeAppender.append("}\n");
				codeAppender.append("}");

				method.setBody(codeAppender.toString());
				return ctClass.toBytecode();
			} else if (CAN_INJECT_NAMES[4].matcher(ctClass.getName()).matches() && type.contains("dubbo")) {
				injectClassGenerator(loader, ctClass.getClassPool());
			} else if (CAN_INJECT_NAMES[5].matcher(ctClass.getName()).matches()) {
				CtMethod method = ClassUtils.getMatchMethod(ctClass, "handleRequest",
						new String[] { "com.alibaba.dubbo.remoting.exchange.ExchangeChannel", "com.alibaba.dubbo.remoting.exchange.Request" });
				if (method != null) {
					String old_name = method.getName();
					String new_name = old_name + "$impl";

					CtMethod method_new = CtNewMethod.copy(method, new_name, ctClass, null);
					// 增加新方法
					ctClass.addMethod(method_new);

					StringBuffer codeAppender = new StringBuffer();
					codeAppender.append("{\n");
					codeAppender.append("System.out.println(\"Calling " + method.getLongName() + "\");\n");
					codeAppender.append("System.out.println($2.getData().getClass());\n");

					codeAppender.append("try{\n");
					codeAppender.append("return " + new_name + "($$);");
					codeAppender.append("}catch(Throwable ex1){\n");
					codeAppender.append("ex1.printStackTrace();\n");
					codeAppender.append("throw ex1;\n");
					codeAppender.append("}finally{\n");

					codeAppender.append("}\n");
					codeAppender.append("}");

					method.setBody(codeAppender.toString());
					return ctClass.toBytecode();
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return null;
	}

	public boolean canInject(String nClassName) {
		for (Pattern name : CAN_INJECT_NAMES) {
			if (name.matcher(nClassName).matches()) {
				return true;
			}
		}
		return false;
	}

	public void setParameters(Map<String, String> params) {
		this.params = params;
	}

	public void init() {

	}

}
